<?php
/**
 * Model for the users table
 */
class Users extends CI_Model
{
	public function __construct()
	{
		$this->load->database();
	}
	
	/**
	 * Activates the user's account. When the user's account is activated,
	 * information about the user's password, security question, and answer
	 * to the security question are added to the user's record. In addition,
	 * the user's temporary password is removed, and it marks a timestamp of
	 * when the user was activated.
	 * 
	 * @param $id        The id of the user.
	 * @param $password  The user's new password.
	 * @param $sq_id     The id of the security question.
	 * @param $sq_answer The user's answer to the security question.
	 */
	public function activate($id, $password, $sq_id = null, $sq_answer = null) // TODO: Put security question back in.
	{
		$this->db->where('id', $id);
		$this->db->set('password', $password);
		//$this->db->set('security_question_id', $sq_id);
		//$this->db->set('security_question_answer', $sq_answer);
		$this->db->set('temp_password', null);
		$this->db->set('temp_password_time', null);
		$this->db->set('active', 1);
		$this->db->set('active_time', 'now()', false); // now() is the MySQL current timestamp function.
		$this->db->update('users');
	}
	
	/**
	 * In order for a user to log in, the email address and password must both
	 * be linked to the same user.
	 * @param $email    The user's email address.
	 * @param $password The password the user put in.
	 * 
	 * @return The authenticated user, or null if the email address and
	 * password do not match.
	 */
	public function authenticate($email, $password)
	{
		$this->db->from('users');
		$this->db->where('email', $email);
		$this->db->where('password', $password); // TODO: Encrypt the password
		$this->db->where('active', 1);
		$query = $this->db->get();
		return ($query->num_rows() > 0) ? $query->row() : null;
	}
	
	/**
	 * An email is sent to a new user with a temporary password and a link to
	 * the page where the user activates his or her account. The temporary
	 * password the user inputs must match the one in the email.
	 * 
	 * Both authenticate_activate and authenticate_reset have a similar purpose
	 * of matching the email address with the temporary password. The
	 * difference is that authenticate_activate is for users that have not
	 * yet been activated, while authenticate_reset is for active users who
	 * have forgotten their passwords.
	 * 
	 * @param $email    The user's email address.
	 * @param $password The temporary password the user put in.
	 * 
	 * @return The authenticated user, or null if the email address and
	 * temporary password do not match.
	 */
	public function authenticate_activate($email, $temp_password)
	{
		$this->db->from('users');
		$this->db->where('email', $email);
		$this->db->where('temp_password', $temp_password); // TODO: Encrypt the password
		$this->db->where('active', 0);
		$query = $this->db->get();
		return ($query->num_rows() > 0) ? $query->row() : null;
	}
	
	/**
	 * When a user forgets his or her password and decides to reset it, an
	 * email is sent to the user with a temporary password and a link to the
	 * page where the user resets his or her password.
	 * 
	 * Both authenticate_activate and authenticate_reset have a similar purpose
	 * of matching the email address with the temporary password. The
	 * difference is that authenticate_activate is for users that have not
	 * yet been activated, while authenticate_reset is for active users who
	 * have forgotten their passwords.
	 * 
	 * @param $email    The user's email address.
	 * @param $password The temporary password the user put in.
	 * 
	 * @return The authenticated user, or null if the email address and
	 * temporary password do not match.
	 */
	public function authenticate_reset($email, $temp_password)
	{
		$this->db->from('users');
		$this->db->where('email', $email);
		$this->db->where('temp_password', $temp_password); // TODO: Encrypt the password
		$this->db->where('active', 1);
		$query = $this->db->get();
		return ($query->num_rows() > 0) ? $query->row() : null;
	}
	
	/**
	 * Checks if the an account with the given email address already exists.
	 * 
	 * @param $email The given email address.
	 * @return True if the email address is already taken; false otherwise.
	 */
	public function email_exists($email)
	{
		$this->db->select('email');
		$this->db->from('users');
		$this->db->where('email', $email);
		$query = $this->db->get();
		return $query->num_rows() > 0;
	}
	
	/**
	 * Determines whether the user with the given id has any of the specified
	 * roles. This is used for restricting web pages to certain kinds of users.
	 * 
	 * @param $id         The id of the user.
	 * @param $role_names A list of roles.
	 * 
	 * @return True if the user's role matches one of the given roles; false
	 * otherwise.
	 */
	public function has_role_with_name_in($id, $role_names)
	{
		$this->db->select('users.id');
		$this->db->from('users');
		$this->db->where('users.id', $id);
		$this->db->join('fk_users_roles', 'users.id = fk_users_roles.user_id');
		$this->db->join('roles', 'fk_users_roles.role_id = roles.id');
		$this->db->where_in('roles.name', $role_names);
		$query = $this->db->get();
		return $query->num_rows() > 0;
	}
	
	/**
	 * Inserts a new user into the database.
	 */
	public function insert($name, $email, $password, $role_id, $school, $city, $state)
	{
		$this->db->set('name', $name);
		$this->db->set('email', $email);
		$this->db->set('password', $password);
		$this->db->set('role_id', $role_id);
		$this->db->set('school', $school);
		$this->db->set('city', $city);
		$this->db->set('state', $state);
		$this->db->set('active', 1);
		$this->db->set('active_time', 'now()', false); // now() is the MySQL current timestamp function.
		$this->db->insert('users');
		return $this->db->insert_id();
	}
	
	/**
	 * @param $id The id of the user.
	 * 
	 * @return The user with the specified id, or null if there is no such user
	 * with that id.
	 */
	public function select_by_id($id)
	{
		$this->db->from('users');
		$this->db->where('id', $id);
		$query = $this->db->get();
		return ($query->num_rows() > 0) ? $query->row() : null;
	}
	
	/**
	 * Selects all the users that go to a specific school and have a specific
	 * role. For example, this can be used to select all high school basketball
	 * players that go to Provo High School.
	 * 
	 * @param $school_id The id of the school.
	 * @param $role_id   The id of the role.
	 * 
	 * @return A list of all such users. If no such user exists, returns an
	 * empty list.
	 */
	public function select_by_school_id($school_id, $role_id)
	{
		$this->db->from('users');
		if ($school_id != 0)
		{
			$this->db->join('fk_schools_users', 'users.id = fk_schools_users.user_id');
			$this->db->where('fk_schools_users.school_id', $school_id);
		}
		if ($role_id != 0)
		{
			$this->db->join('fk_users_roles', 'users.id = fk_users_roles.user_id');
			$this->db->where('fk_users_roles.role_id', $role_id);
		}
		$query = $this->db->get();
		return $query->result_array();
	}
	
	/**
	 * Sends a temporary password to the user with the given email address.
	 * 
	 * @param  $email The user's email address.
	 */
	public function create_temporary_password($email)
	{
		$this->db->where('email', $email);
		$temporary_password = $this->generate_temporary_password();
		$this->db->set('temp_password', $temporary_password);
		$this->db->set('temp_password_time', 'now()', false); // Timestamp the temporary password.
		$this->db->update('users');
		return $temporary_password;
	}
	
	// -------------------- PRIVATE METHODS -------------------- //
	
	private function generate_temporary_password()
	{
		$TEMP_PASSWORD_LENGTH = 16;
		$temporary_password = ''; // Initialization.
		for ($i = 0; $i < $TEMP_PASSWORD_LENGTH; $i++)
		{
			$temporary_password .= $this->to_base62(rand(0, 61));
		}
		return $temporary_password;
	}
	
	/**
	 * Converts an integer (mod 62) to a base-62 number (0-9,A-Z,a-z)
	 * @param $num An integer between 1 and 62 (inclusive)
	 * @return the base-62 digit.
	 */
	private function to_base62($num)
	{
		$num %= 62;
		if ($num < 10)
		{
			return chr($num + 48);
		}
		else if ($num < 36)
		{
			return chr($num + 55);
		}
		else
		{
			return chr($num + 61);
		}
	}
}